// 日志系统实现
// 参考 xv6-riscv 实现，提供写前日志和崩溃恢复

#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "string.h"
#include "fs/buf.h"
#include "fs/fs.h"

static void recover_from_log(void);
static void commit(void);
static void read_head(void);
static void write_head(void);
static void install_trans(void);
static void write_log(void);

extern struct superblock sb;

struct log {
  struct spinlock lock;
  int start;           // 日志区起始块号
  int size;            // 日志区大小（块数）
  int outstanding;     // 未完成的系统调用数
  int committing;      // 是否正在提交
  int dev;             // 设备号
  struct logheader lh; // 日志头（内存副本）
};
struct log log;

// 初始化日志系统
void
initlog(int dev, struct superblock *sb)
{
  if (sizeof(struct logheader) >= BSIZE)
    panic("initlog: too big logheader");

  initlock(&log.lock, "log");
  log.start = sb->logstart;
  log.size = sb->nlog;
  log.dev = dev;
  log.outstanding = 0;
  log.committing = 0;
  log.lh.n = 0;
  recover_from_log();
}

// 从日志中恢复
static void
recover_from_log(void)
{
  read_head();
  install_trans(); // 如果已提交，从日志复制到文件系统
  log.lh.n = 0;
  write_head(); // 清除日志
}

// 读取日志头
static void
read_head(void)
{
  struct buf *buf = bread(log.dev, log.start);
  struct logheader *lh = (struct logheader *) (buf->data);
  int i;
  log.lh.n = lh->n;
  for (i = 0; i < log.lh.n; i++) {
    log.lh.block[i] = lh->block[i];
  }
  brelse(buf);
}

// 写入日志头
static void
write_head(void)
{
  struct buf *buf = bread(log.dev, log.start);
  struct logheader *lh = (struct logheader *) (buf->data);
  int i;
  lh->n = log.lh.n;
  for (i = 0; i < log.lh.n; i++) {
    lh->block[i] = log.lh.block[i];
  }
  bwrite(buf);
  brelse(buf);
}

// 将日志中的块复制到文件系统
static void
install_trans(void)
{
  int tail;

  for (tail = 0; tail < log.lh.n; tail++) {
    struct buf *lbuf = bread(log.dev, log.start + tail + 1); // 读取日志块
    struct buf *dbuf = bread(log.dev, log.lh.block[tail]);   // 读取目标块
    memmove(dbuf->data, lbuf->data, BSIZE);  // 复制
    bwrite(dbuf);  // 写入磁盘
    brelse(lbuf);
    brelse(dbuf);
  }
}

// 开始一个系统调用的事务
void
begin_op(void)
{
  acquire(&log.lock);
  while(1){
    if(log.committing){
      sleep(&log, &log.lock);
    } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){
      // 日志空间不足，等待提交
      sleep(&log, &log.lock);
    } else {
      log.outstanding += 1;
      release(&log.lock);
      break;
    }
  }
}

// 结束一个系统调用的事务
void
end_op(void)
{
  int do_commit = 0;

  acquire(&log.lock);
  log.outstanding -= 1;
  if(log.committing)
    panic("log.committing");
  if(log.outstanding == 0){
    do_commit = 1;
    log.committing = 1;
  } else {
    // 唤醒等待的 begin_op
    wakeup(&log);
  }
  release(&log.lock);

  if(do_commit){
    // 提交事务
    commit();
    acquire(&log.lock);
    log.committing = 0;
    wakeup(&log);
    release(&log.lock);
  }
}

// 将块写入日志
void
log_write(struct buf *b)
{
  int i;

  // 检查日志是否已初始化
  if(log.size == 0) {
    panic("log_write: log not initialized");
  }

  if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
    panic("too big a transaction");
  if (log.outstanding < 1)
    panic("log_write outside of trans");

  acquire(&log.lock);
  for (i = 0; i < log.lh.n; i++) {
    if (log.lh.block[i] == b->blockno)   // 块已在日志中
      break;
  }
  log.lh.block[i] = b->blockno;
  if (i == log.lh.n) {
    bpin(b);
    log.lh.n++;
  }
  release(&log.lock);
}

// 提交事务
static void
commit(void)
{
  if (log.lh.n > 0) {
    write_log();     // 将修改的块写入日志
    write_head();    // 写入日志头
    install_trans(); // 从日志安装到文件系统
    log.lh.n = 0;
    write_head();    // 清除日志头
  }
}

// 将修改的块复制到日志
static void
write_log(void)
{
  int tail;

  for (tail = 0; tail < log.lh.n; tail++) {
    struct buf *to = bread(log.dev, log.start + tail + 1); // 日志块
    struct buf *from = bread(log.dev, log.lh.block[tail]); // 缓存块
    memmove(to->data, from->data, BSIZE);
    bwrite(to);  // 写入日志
    brelse(from);
    brelse(to);
  }
}